在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息就会涉及到“资源管理”的问题。FreeRTOS对此提供了一个叫做“队列”的机制来完成任务与任务、任务与中断之间的消息传递。

  • 队列简介
  • 队列结构体
  • 队列创建
  • 向队列发送消息
  • 队列上锁和解锁
  • 从队列读取消息

队列简介

队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。 由于队列用来传递消息的,所以也称为消息队列。 FreeRTOS 中的信号量的也是依据队列实现的。

数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中, 这就意味着在队列中存储的是数据的原始值, 而不是原数据的引用(即只传递数据的指针),这个也叫做值传递。 学过UCOS 的同学应该知道, UCOS 的消息队列采用的是引用传递,传递的是消息指针。采用引用传递的话消息内容就必须一直保持可见性,也就是消息内容必须有效,那么局部变量这种可能会随时被删掉的东西就不能用来传递消息,但是采用引用传递会节省时间。

采用值传递的话虽然会导致数据拷贝,会浪费一点时间,但是一旦将消息发送到队列中原始的数据缓冲区就可以删除掉或者覆写,这样的话这些缓冲区就可以被重复的使用。 FreeRTOS中使用队列传递消息的话虽然使用的是数据拷贝,但是也可以使用引用来传递消息啊,直接往队列中发送指向这个消息的地址指针就可以了。这样当我要发送的消息数据太大的时候就可以直接发送消息缓冲区的地址指针,比如在网络应用环境。

数据存储

通常队列采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据的时候(也叫入队)永远都是发送到队列的尾部,而从队列提取数据的时候(也叫出队)是从队列的头部提取的。但是也可以使用 LIFO 的存储缓冲,也就是后进先出, FreeRTOS 中的队列也提供了LIFO 的存储缓冲机制。

多任务访问

队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。

出队阻塞

当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间。出队就是就从队列中读取消息,出队阻塞是针对从队列中读取消息的任务而言的。

入队阻塞

入队说的是向队列中发送消息, 将消息加入到队列中。和出队阻塞一样,当一个任务向队列发送消息的话也可以设置阻塞时间。

队列结构体

有一个结构体用于描述队列,叫做 Queue_t,这个结构体在文件 queue.c 中定义

队列创建

在使用队列之前必须先创建队列, 有两种创建队列的方法,
一种是静态的,使用函数xQueueCreateStatic()队列所需要的内存由用户自行分配。
另一个是动态的,使用函数 xQueueCreate()。

这两个函数本质上都是宏,真正完成队列创建的函数是 xQueueGenericCreate()和 xQueueGenericCreateStatic(),这两个函数在文件 queue.c 中有定义。

队列初始化函数

队列初始化函数 prvInitialiseNewQueue()用于队列的初始化,此函数在文件 queue.c 中有定义。

1
2
3
4
5
6
7
8
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, //队列长度
const UBaseType_t uxItemSize, //队列项目长度
uint8_t * pucQueueStorage, //队列项目存储区
const uint8_t ucQueueType, //队列类型
Queue_t * pxNewQueue ) //队列结构体
{

}

队列复位函数

队列初始化函数 prvInitialiseNewQueue()中调用了函数 xQueueGenericReset()来复位队列。

向队列发送消息

分类 函数 描述
任务级入队函数 xQueueSend() 发送消息到队列尾部( 后向入队),这两个函数是一样的。
任务级入队函数 xQueueSendToBack() -
任务级入队函数 xQueueSendToFront() 发送消息到队列头( 前向入队)。
任务级入队函数 xQueueOverwrite() 发送消息到队列, 带覆写功能, 当队列满了以后自动覆盖掉旧的消息。

|中断级入队函数|xQueueSendFromISR()| 发送消息到队列尾( 后向入队), 这两个数函数是一样的,用于中断服务函|
|中断级入队函数|xQueueSendToBackFromISR() |-|
|中断级入队函数|xQueueSendToFrontFromISR()|发送消息到队列头( 前向入队), 用于中断服务函数|
|中断级入队函数|xQueueOverwriteFromISR()|发送消息到队列, 带覆写功能|

当队列满了以后自动覆盖掉旧的消息,用于中断服务函数。

任务级通用入队函数

函数 xQueueSend()、 xQueueSendToBack()和 xQueueSendToFront()
这三个函数都是用于向队列中发送消息的,这三个函数本质都是宏。

函数 xQueueSend()和 xQueueSendToBack()是一样的,都是后向入队,即将新的消息插入到队列的后面。

函数xQueueSendToToFront()是前向入队,即将新消息插入到队列的前面。

这三个函数最后都是调用的同一个函数: xQueueGenericSend(),只能用于任务函数中,不能用于中断服务函数,中断服务函数有专用的函数,它们以“ FromISR”结尾。

中断级通用入队函数

中断级入队函数 xQueueGenericSendFromISR(),其他的中断级入队函数都是靠此函数来实现的。

队列上锁和解锁

队列的上锁和解锁是两个 API 函数: prvLockQueue()和 prvUnlockQueue()。

队列上锁函数 prvLockQueue(),此函数本质上就是一个宏,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{
if( ( pxQueue )->cRxLock == queueUNLOCKED )
{
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;
}

if( ( pxQueue )->cTxLock == queueUNLOCKED )
{
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;
}
}
taskEXIT_CRITICAL()

prvLockQueue()函数很简单,就是将队列中的成员变量 cRxLock 和 cTxLock 设置为queueLOCKED_UNMODIFIED 就行了。

从队列读取消息

分类 函数 描述
任务级出队函数 xQueueReceive() 从队列中读取队列项(消息),并且读取完以后删除掉队列项(消息)
任务级出队函数 xQueuePeek() 从队列中读取队列项(消息),并且读取完以后不删除队列项(消息)
中断级出队函数 xQueueReceiveFromISR() 从队列中读取队列项(消息),并且读取完以后删除掉队列项(消息),用于中断服务函数中
中断级出队函数 xQueuePeekFromISR () 从队列中读取队列项(消息),并且读取完以后不删除队列项(消息),用于中断服务函数中。